package grpcx

import (
	"context"
	"fmt"
	"gitee.com/xiao_hange/go-admin-pkg/pkg/logger"
	"gitee.com/xiao_hange/go-admin-pkg/pkg/netx"
	consulapi "github.com/hashicorp/consul/api"
	uuid "github.com/satori/go.uuid"
	etcdv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/naming/endpoints"
	"google.golang.org/grpc"
	"google.golang.org/grpc/health"
	healthv1 "google.golang.org/grpc/health/grpc_health_v1"
	"net"
	"strconv"
	"time"
)

type Server struct {
	*grpc.Server
	Port           int
	EtcdAddrs      []string
	ConsulAddrs    string
	Name           string
	IpOrServerName string
	L              logger.GrpcLogger
	IsEtcdClient   bool
	Weight         int
	Label          string
	ServerName     string

	etcdClient     *etcdv3.Client
	consulClient   *consulapi.Client
	key            string
	addr           string
	ipOrServerName string
	kaCancel       func()
	serverID       string
	em             endpoints.Manager
}

func (s *Server) Serve() error {
	l, err := net.Listen("tcp", ":"+strconv.Itoa(s.Port))
	if err != nil {
		return err
	}
	//s.IpOrServerName = netx.GetOutboundIP() 由于 Docker 网络问题IP指定传入
	if s.IpOrServerName == "" {
		s.ipOrServerName = netx.GetOutboundIP()
	} else {
		s.ipOrServerName = s.IpOrServerName
	}

	s.addr = s.ipOrServerName + ":" + strconv.Itoa(s.Port)

	if s.IsEtcdClient == true {
		err = s.etcdRegister()
	} else {
		err = s.consulRegister()
	}
	if err != nil {
		return err
	}
	fmt.Println(fmt.Sprintf("[ %s 服务启动 ] %s 成功", s.ServerName, s.addr))
	return s.Server.Serve(l)
}

func (s *Server) etcdRegister() error {
	client, err := etcdv3.New(etcdv3.Config{
		Endpoints: s.EtcdAddrs,
	})
	if err != nil {
		return err
	}
	s.etcdClient = client
	// endpoint 以服务为维度。一个服务一个 Manager
	em, err := endpoints.NewManager(client, "service/"+s.Name)
	if err != nil {
		return err
	}

	key := "service/" + s.Name + "/" + s.addr
	s.key = key
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	// 你可以做成配置的
	var ttl int64 = 30
	leaseResp, err := client.Grant(ctx, ttl)

	ctx, cancel = context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	err = em.AddEndpoint(ctx, key, endpoints.Endpoint{
		Addr: s.addr,
		Metadata: map[string]string{
			"weight": strconv.Itoa(s.Weight),
			"label":  s.Label,
		},
	}, etcdv3.WithLease(leaseResp.ID))

	kaCtx, kaCancel := context.WithCancel(context.Background())
	s.kaCancel = kaCancel
	ch, err := client.KeepAlive(kaCtx, leaseResp.ID)
	if err != nil {
		return err
	}
	go func() {
		for kaResp := range ch {
			s.L.Warn("续约：", logger.String("resp", kaResp.String()))
		}
	}()
	return nil
}

func (s *Server) consulRegister() error {
	cfg := consulapi.DefaultConfig()
	cfg.Address = s.ConsulAddrs
	var err error
	s.consulClient, err = consulapi.NewClient(cfg)

	if err != nil {
		return err
	}
	if err != nil {
		panic(err)
	}
	check := &consulapi.AgentServiceCheck{
		GRPC:                           s.addr,
		Timeout:                        "5s",
		Interval:                       "5s",
		DeregisterCriticalServiceAfter: "10s",
	}
	s.serverID = fmt.Sprintf("%s", uuid.NewV4())
	//生成注册对象
	registration := &consulapi.AgentServiceRegistration{
		Name:    s.Name,
		ID:      s.serverID,
		Port:    s.Port,
		Tags:    []string{s.Name},
		Address: s.ipOrServerName,
		Check:   check,
		Meta: map[string]string{
			"weight": strconv.Itoa(s.Weight),
			"label":  s.Label,
		},
	}
	err = s.consulClient.Agent().ServiceRegister(registration)
	healthv1.RegisterHealthServer(s.Server, health.NewServer())
	if err != nil {
		panic(err)
	}

	return nil
}

func (s *Server) Close() error {
	var err error
	if s.IsEtcdClient {
		err = s.etcdClose()
	} else {
		err = s.consulClose()
	}
	s.Server.GracefulStop()
	fmt.Println(fmt.Sprintf("[ %s 服务注销 ] %s 成功", s.ServerName, s.addr))
	return err
}
func (s *Server) etcdClose() error {
	if s.kaCancel != nil {
		s.kaCancel()
	}
	if s.em != nil {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		err := s.em.DeleteEndpoint(ctx, s.key)
		if err != nil {
			return err
		}
	}
	if s.etcdClient != nil {
		err := s.etcdClient.Close()
		if err != nil {
			return err
		}
	}
	return nil
}
func (s *Server) consulClose() error {
	err := s.consulClient.Agent().ServiceDeregister(s.serverID)
	return err
}
